'use strict';

const fs = require('fs');
const path = require('path');
const userHome = require('user-home');
const prompts = require('prompts');
const fsExtra = require('fs-extra');
const semver = require('semver');
const ejs = require('ejs');
const glob = require('glob');

const Command = require('@caiheping-cli-dev/command');
const Package = require('@caiheping-cli-dev/package');
const { spinnerStart, sleep, execAsync } = require('@caiheping-cli-dev/utils');
const log = require('@caiheping-cli-dev/log');

const getProjectTemplate = require('./getProjectTemplate');

const TYPE_PROJECT = 'project';
const TYPE_COMPONENT = 'component';
const TEMPLATE_TYPE_NORMAL = 'normal'; // 标准安装
const TEMPLATE_TYPE_CUSTOM = 'custom'; // 自定义安装

const WHITE_COMMAND = ['npm', 'cnpm', 'yarn']; // 白名单命令

class InitCommand extends Command{
    init() {
        this.projectName = this._argv[0] || '';
        this.force = !!this._argv[1].force;
        log.verbose('projectName', this.projectName);
        log.verbose('force', this.force);
    }

    async exec() {
        // console.log('init 的业务逻辑');
        try {
            // 准备阶段
            await this.prepare()
            // 获取项目信息
            await this.getProjectInfo()
            // 下载模板
            if (this.projectInfo) {
                await this.downLoadTemplate();
            }
            // 安装模板
            await this.installTemplate();
        } catch (err) {
            if (process.env.LOG_LEVEL === 'verbose') {
                console.log(err);
            } else {
                log.error(err);
            }
        }
    }

    async prepare() {
        // this.templates = await getProjectTemplate();
        this.templates = [{
            name: 'vue3项目模板',
            npmName: 'caiheping-cli-vue3-template',
            version: '1.0.0',
            type: 'normal',
            installCommand: 'cnpm install',
            startCommand: 'npm run dev'

        }]
        if (!this.templates || !Array.isArray(this.templates) || this.templates.length === 0) {
            throw new Error('模板不存在！');
        }
        
        // 当前执行命令的路径
        const localPath = process.cwd();
        // 判断目录是否有值, 有
        if (this.hasFiles(localPath)) {
            let ifContinue = false;
            if (!this.force) {
                ifContinue = (await prompts(
                    {
                        type: 'confirm',
                        name: 'ifContinue',
                        message: '当前文件夹不为空，是否继续创建项目？',
                        initial: false
                    }
                )).ifContinue;
                if (!ifContinue) {
                    return false;
                }
            }
            if(ifContinue || this.force){
                const { confirmDelete } = await prompts([{
                    type: 'confirm',
                    name: 'confirmDelete',
                    default: false,
                    message: '是否确认清空当前目录下的文件？'
                }]);
                if (confirmDelete) {
                    // 是否启动强制更新
                    fsExtra.emptyDirSync(localPath);
                }
            }
        }
    }

    async installTemplate() {
        // console.log(this.templateInfo);
        if(this.templateInfo) {
            if (!this.templateInfo.type) {
                this.templateInfo.type = TEMPLATE_TYPE_NORMAL;
            }
            if (this.templateInfo.type === TEMPLATE_TYPE_NORMAL) {
                await this.installNormalTemplate();
            } else if (this.templateInfo.type === TEMPLATE_TYPE_CUSTOM) {
                await this.installCustomTemplate();
            }
        } else {
            throw new Error('项目模板消息不存在！')
        }
    }
    // ejs渲染模板
    async ejsRender(options) {
        // console.log('ejsRender', this.projectInfo);
        const projectInfo = this.projectInfo;
        return new Promise((resolve, reject) => {
            glob('**', {
                cwd: options.cwd,
                ignore: options.ignore || '',
                nodir: true
            }, (err, files) => {
                if(err) {
                    reject(err);
                }
                Promise.all(files.map(file => {
                    const filePath = path.join(options.cwd, file);
                    return new Promise((resolve1, reject1) => {
                        ejs.renderFile(filePath, projectInfo, {}, (err, result) => {
                            // console.log(err, result);
                            if(err) {
                                reject1(err);
                            } else {
                                // ejs不会改变源文件，要把结果重新写入
                                fsExtra.writeFileSync(filePath, result);
                                resolve1(result);
                            }
                        })
                    })
                })).then(() => {
                    resolve();
                }).catch(err => {
                    reject(err);
                })
            })
        })
    }
    // 安装标准模板
    async installNormalTemplate() {
        let spinner = spinnerStart('正在安装模板...');
        try {
            await sleep();
            const templatePath = path.resolve(this.templateNpm.cacheFilePath, 'template');
            const targetPath = process.cwd();
            fsExtra.ensureDirSync(templatePath);
            fsExtra.ensureDirSync(targetPath);
            fsExtra.copySync(templatePath, targetPath);
        } catch (err) {
            throw err
        } finally {
            spinner.stop(true);
            log.info('模板安装完成');
        }
        const options = {
            cwd: process.cwd(),
            ignore: ['node_modules/**', 'src/**'],
        };
        await this.ejsRender(options);
        // 依赖安装
        const { installCommand, startCommand } = this.templateInfo;
        await this.execCommand(installCommand, '依赖安装过程中失败！');
        await this.execCommand(startCommand, '服务启动异常！');
    }
    // 安装自定义模板
    async installCustomTemplate() {
        console.log('安装自定义模板');
    }

    // 封装执行脚本的方法
    async execCommand(command, describe='error') {
        let res;
        if(command) {
            const cmd = this.checkCommand(command.split(' ')[0]);
            if(!cmd) {
                throw new Error(`${command} 命令异常`);
            }
            const args = command.split(' ').slice(1);
            res = await execAsync(cmd, args, {
                stdio: 'inherit',
                cwd: process.cwd()
            });
        }
        if (res !== 0) {
            throw new Error(describe);
        }
    }

    checkCommand(cmd) {
        return WHITE_COMMAND.includes(cmd) ? cmd : null;
    }

    async downLoadTemplate() {
        const { template } = this.projectInfo;
        this.templateInfo = this.templates.find(item => item.npmName === template);
        const { npmName, version } = this.templateInfo
        const targetPath = path.resolve(userHome, '.caiheping-cli-dev', 'template');
        const storeDir = path.resolve(userHome, '.caiheping-cli-dev', 'template', 'node_modules');
        this.templateNpm = new Package({
            targetPath,
            storeDir,
            packageName: npmName,
            packageVersion: version
        });
        if (!await this.templateNpm.exists()) {
            const spinner = spinnerStart('正在下载模板');
            try {
                await this.templateNpm.install();
                await sleep();
            } catch (err) {
                throw new Error(err);
            } finally {
                spinner.stop(true);
                if (await this.templateNpm.exists()) {
                    log.info('下载成功');
                }
            }
        } else {
            const spinner = spinnerStart('正在更新模板');
            try {
                await this.templateNpm.update();
                await sleep();
            } catch (err) {
                throw new Error(err);
            } finally {
                spinner.stop(true);
                if (await this.templateNpm.exists()) {
                    log.info('更新成功');
                }
            }
        }
        
    }

    async getProjectInfo() {
        const proTemplates = this.templates.map(item => {
            return {
                title: item.name,
                value: item.npmName
            }
        })
        // 选择创建项目或组件
        this.projectInfo = await prompts([{
            type: 'select',
            name: 'type',
            message: '请选择项目初始化类型',
            choices: [{
                title: '项目',
                value: TYPE_PROJECT
            }, {
                title: '组件',
                value: TYPE_COMPONENT
            }]
        }, {
            type: 'select',
            name: 'template',
            message: '请选择项目初模板',
            choices: proTemplates
        }, {
            type: 'text',
            name: 'projectName',
            message: '请输入项目名称',
            initial: '',
            validate: function(v) {
                return !/^[a-zA-Z]+(([-]?[a-zA-Z0-9]+[_]?[a-zA-Z0-9]|[_]?[a-zA-Z0-9]+[-]?[a-zA-Z0-9]|[a-zA-Z0-9]+)*)$/.test(v) ? '项目名称不合法' : true;
            }
        }, {
            type: 'text',
            name: 'projectVersion',
            message: '请输入项目版本号',
            initial: '1.0.0',
            validate: function(v) {
                return !!!semver.valid(v) ? '项目版本号不合法' : true;
            },
            format: (v) => semver.valid(v)
        }]);

        return this.projectInfo;
    }

    hasFiles(localPath) {
        let fileList = fs.readdirSync(localPath);
        fileList = fileList.filter(file => !file.startsWith('.') && ['node_modules'].indexOf(file) < 0);
        return fileList && fileList.length > 0;
    }
}

function init(argv) {
    return new InitCommand(argv);
}

module.exports.InitCommand = InitCommand;

module.exports = init;
